package com.wang.job.handler;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Constants;
import com.wang.common.core.constants.Constant;
import com.wang.job.domain.exam.Exam;
import com.wang.job.domain.message.Message;
import com.wang.job.domain.message.MessageText;
import com.wang.job.domain.user.UserScore;
import com.wang.job.domain.message.vo.MessageTextVO;
import com.wang.job.mapper.exam.ExamMapper;
import com.wang.job.mapper.user.UserExamMapper;
import com.wang.job.mapper.user.UserSubmitMapper;
import com.wang.job.service.IMessageService;
import com.wang.job.service.IMessageTextService;
import com.wang.common.core.constants.CacheConstants;
import com.wang.common.redis.service.RedisService;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.*;

@Slf4j
@Component
public class ExamXxlJob {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private UserSubmitMapper userSubmitMapper;

    @Autowired
    private RedisService redisService;

    @Autowired
    private UserExamMapper userExamMapper;

    @Autowired
    private IMessageTextService messageTextService;

    @Autowired
    private IMessageService messageService;
    @XxlJob("examListOrganizeHandler")//每个执行调度任务写一个方法,使用该注解即可
    /*
      每天1点对缓存中的竞赛列表进行刷新
     */
    public void examListOrganizeHandler(){
        log.info("*** examListOrganizeHandler ***");

        List<Exam> unfinished;
        //查询未完赛的竞赛列表
        unfinished = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .gt(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        //刷新未完赛竞赛列表的缓存
        refreshCache(unfinished,CacheConstants.EXAM_UNFINISHED_LIST);


        List<Exam> history;
        //查询历史竞赛列表
        history = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle, Exam::getStartTime, Exam::getEndTime)
                .le(Exam::getEndTime, LocalDateTime.now())
                .eq(Exam::getStatus, Constants.TRUE)
                .orderByDesc(Exam::getCreateTime));
        //刷新历史竞赛列表的缓存
        refreshCache(history,CacheConstants.EXAM_HISTORY_LIST);
    }

    /**
     * 定时发送站内信,公布竞赛结果
     */
    @XxlJob("examResultHandler")
    public void examResultHandler(){

        LocalDateTime nowTime = LocalDateTime.now();//当前时间

        LocalDateTime minusDateTime = nowTime.minusDays(1);//获取到当前时间前一天的时间

        List<Exam> examList = examMapper.selectList(new LambdaQueryWrapper<Exam>()
                .select(Exam::getExamId, Exam::getTitle)
                .eq(Exam::getStatus, Constants.TRUE)
                .ge(Exam::getEndTime, minusDateTime)
                .le(Exam::getEndTime, nowTime));//查询在当前时间和前一天已经完赛的竞赛

        if (CollectionUtil.isEmpty(examList)){
            return;
        }
        //获取到竞赛的id

        Set<Long> examIdSet = new HashSet<>();
        for (Exam exam : examList){
            examIdSet.add(exam.getExamId());
        }

        //获取到该时间段范围之内的竞赛结果,即每个用户在当前竞赛中的成绩
        List<UserScore> userScoreList = userSubmitMapper.selectUserScoreList(examIdSet);

        //把获取到的结果按照examId进行分组
        Map<Long, List<UserScore>> userScoreMap = new HashMap<>();
        for (UserScore userScore : userScoreList) {
            Long examId = userScore.getExamId();
            if (!userScoreMap.containsKey(examId)) {
                userScoreMap.put(examId, new ArrayList<>());
            }
            userScoreMap.get(examId).add(userScore);
        }

        //根据每个竞赛的结果创建站内信
        createMessage(examList,userScoreMap);
    }

    /**
     * 创建站内信,并缓存竞赛结果
     * @param examList 在指定时间范围之内竞赛的详细信息
     * @param userScoreMap 每个竞赛的结果(每个竞赛用户的分数)
     */
    private void createMessage(List<Exam> examList,Map<Long, List<UserScore>> userScoreMap){

        //创建两个类型的的列表,方便后面批量插入
        List<MessageText> messageTextList = new ArrayList<>();
        List<Message> messageList = new ArrayList<>();

        //归纳每个竞赛结果的详细信息
        for (Exam exam : examList){
            Long examId = exam.getExamId();
            List<UserScore> userScoreList = userScoreMap.get(examId);
            //对每个用户的信息进行详细处理
            int totalUser = userScoreList.size();
            int examRank = 1;//当前用户在当前竞赛中的排名
            for (UserScore userScore : userScoreList) {
                String msgTitle =  exam.getTitle() + "——排名情况";
                String msgContent = "您所参与的竞赛：" + exam.getTitle()
                        + "，本次参与竞赛一共" + totalUser + "人， 您排名第"  + examRank + "名！";

                //设置用户的排名
                userScore.setExamRank(examRank);

                //设置消息详细信息
                MessageText messageText = new MessageText();
                messageText.setMessageTitle(msgTitle);
                messageText.setMessageContent(msgContent);
                messageText.setCreateBy(Constant.SYSTEM_USER_ID);
                messageTextList.add(messageText);

                //设置消息接收方与发送方
                Message message = new Message();
                message.setSendId(Constant.SYSTEM_USER_ID);
                message.setCreateBy(Constant.SYSTEM_USER_ID);
                message.setRecId(userScore.getUserId());
                messageList.add(message);
                examRank++;//排名加一名
            }

            //在数据库中设置用户在当前竞赛中的排名
            userExamMapper.updateUserScoreAndRank(userScoreList);

            //把当前竞赛的排名信息设置进入缓存中,以便用户查看
            redisService.rightPushAll(getExamRankListKey(examId),userScoreList);
        }
        //循环进行完成之后,messageTextList中保存的是当前竞赛中每个用户的完整的消息
        //messageList保存的是这个竞赛中完整的发送人和接收人的信息,与messageTextList一一对应
        //批量插入messageText数据,在插入数据之后会对数据进行回填
        messageTextService.BatchInsert(messageTextList);
        //设置message类中的textId,并构建MessageText的Map,以便后面进行redis插入
        Map<String, MessageTextVO> messageTextVOMap = new HashMap<>();
        for (int i = 0;i < messageTextList.size();i++){
            MessageText messageText = messageTextList.get(i);
            messageList.get(i).setTextId(messageText.getTextId());
            MessageTextVO messageTextVO = new MessageTextVO();
            BeanUtil.copyProperties(messageText,messageTextVO);
            String msgDetailKey = getMsgDetailKey(messageText.getTextId());
            messageTextVOMap.put(msgDetailKey,messageTextVO);
        }
        //Message类设置完整之后,批量插入
        messageService.BatchInsert(messageList);
        //把messageTextVOMap插入缓存中
        redisService.multiSet(messageTextVOMap);
        //把List<Message>中的信息按照recId分组
        Map<Long, List<Message>> userMsgMap = new HashMap<>();
        for (Message message : messageList) {
            Long recId = message.getRecId(); //获取当前消息的接收者ID
            // 检查Map是否已存在该recId的分组
            if (!userMsgMap.containsKey(recId)) {
                // 如果没有，就新建一个空List
                userMsgMap.put(recId, new ArrayList<>());
            }
            // 将该消息添加到对应的List中
            userMsgMap.get(recId).add(message);
        }

        //获取到Map中的所有信息,并缓存进入redis
        Set<Map.Entry<Long, List<Message>>> entrySet = userMsgMap.entrySet();
        for (Map.Entry<Long, List<Message>> entry : entrySet){
            //获取到接收者id
            Long recId = entry.getKey();
            String userMsgListKey = getUserMsgListKey(recId);
            //获取到当前接受者应该接收到的所有的消息
            List<Message> value = entry.getValue();
            List<Long> userIdList = new ArrayList<>();
            //往redis中缓存的是消息的textId
            for (Message message : value){
                userIdList.add(message.getTextId());
            }
            //缓存进入redis
            redisService.rightPushAll(userMsgListKey,userIdList);
        }
    }
    //存储竞赛列表id的key
    private String getDetailKey(Long examId) {
        return CacheConstants.EXAM_DETAIL + examId;
    }

    /**
     * 刷新缓存中的 竞赛详细信息列表 与 竞赛id列表
     * @param examList 竞赛详细信息列表
     * @param examListKey 不同类型的竞赛的key (未完赛竞赛/历史竞赛)
     */
    private void refreshCache(List<Exam> examList,String examListKey){
        if (CollectionUtil.isEmpty(examList)){
            return;
        }

        Map<String,Exam> examMap = new HashMap<>();
        List<Long> examIdList = new ArrayList<>();
        for (Exam exam: examList){
            //先拿到竞赛详情key, 然后根据key往缓存中存竞赛详情
            examMap.put(getDetailKey(exam.getExamId()),exam);
            //存储竞赛id到竞赛id列表
            examIdList.add(exam.getExamId());
        }
        //刷新竞赛详情列表
        redisService.multiSet(examMap);

        //刷新竞赛id列表
        redisService.deleteObject(examListKey);
        redisService.setCacheObject(examListKey, examIdList);
    }

    //设置竞赛排名的缓存key
    private String getExamRankListKey(Long examId) {
        return CacheConstants.EXAM_RANK_LIST + examId;
    }

    //得到用户站内信详情的key
    private String getMsgDetailKey(Long textId) {
        return CacheConstants.MESSAGE_DETAIL + textId;
    }

    //得到每个用户的消息列表的key
    private String getUserMsgListKey(Long userId) {
        return CacheConstants.USER_MESSAGE_LIST + userId;
    }

}
